package com.ds.infrastructure.hyperspace.container.endpoint.destination;

import com.ds.infrastructure.hyperspace.common.EndpointProtocolEnum;
import com.ds.infrastructure.hyperspace.common.config.TargetNodeConfig;
import com.ds.infrastructure.hyperspace.common.config.TargetProtocolConfig;
import com.ds.infrastructure.hyperspace.container.endpoint.connector.HyperspaceConnector;
import com.ds.infrastructure.hyperspace.container.endpoint.connector.NettyEndpointNodeStream;
import com.ds.infrastructure.hyperspace.container.flow.context.HyperspaceDuplexFlowContext;
import com.ds.infrastructure.hyperspace.container.internal.NettyContainer;
import com.ds.infrastructure.hyperspace.container.internal.http2.HyperspaceHttp2Codec;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpClientCodec;
import lombok.extern.slf4j.Slf4j;

import static com.ds.infrastructure.hyperspace.common.EndpointProtocolEnum.*;
import static com.ds.infrastructure.hyperspace.container.endpoint.NettyHandlerName.*;

/**
 * @author  Weishaoying
 */
@Slf4j
public class NettyTargetEndpointChannelInitializer extends ChannelInitializer<Channel> {

    private final NettyContainer nettyContainer;

    private final TargetEndpointNode targetEndpointNode;

    private final HyperspaceConnector hyperspaceConnector;

    public NettyTargetEndpointChannelInitializer(NettyContainer nettyContainer,
            HyperspaceConnector hyperspaceConnector,
            TargetEndpointNode targetEndpointNode) {
        this.nettyContainer = nettyContainer;
        this.hyperspaceConnector = hyperspaceConnector;
        this.targetEndpointNode = targetEndpointNode;
    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        TargetNodeConfig nodeConfig = targetEndpointNode.getNodeConfig();
        TargetProtocolConfig protocolConfig = nodeConfig.getProtocolConfig();
        EndpointProtocolEnum protocolCluster = nodeConfig.getProtocol();
        if (protocolCluster.getIndex() >= HTTP2.getIndex()) {
            configHttp2Pipeline(pipeline, protocolConfig);
            return;
        }
        if (HTTP == protocolCluster || WEBSOCKET == protocolCluster) {
            pipeline.addLast(TARGET_HTTP1_CODEC_HANDLER_NAME, new HttpClientCodec(
                    protocolConfig.getMaxInitialLineLength(),
                    protocolConfig.getMaxHeaderSize(),
                    protocolConfig.getMaxChunkSize(),
                    false,
                    false,
                    protocolConfig.getInitialBufferSize()));
            pipeline.addLast(TARGET_HANDLER_ADAPTER_NAME,
                    new Http1TargetProtocolAdapter(this, nettyContainer,
                            hyperspaceConnector, targetEndpointNode));
            return;
        }
        ch.flush().close();
    }

    private void configHttp2Pipeline(ChannelPipeline pipeline, TargetProtocolConfig protocolConfig) {
        HyperspaceHttp2Codec hyperspaceHttp2Codec = new HyperspaceHttp2Codec(false, protocolConfig);
        pipeline.addLast(TARGET_HTTP2_CODEC_HANDLER_NAME, hyperspaceHttp2Codec);
        pipeline.addLast(TARGET_HANDLER_ADAPTER_NAME, new Http2TargetProtocolAdapter(
                nettyContainer, hyperspaceConnector, targetEndpointNode, hyperspaceHttp2Codec));
    }

    public void switchProtocolFromHttp(NettyEndpointNodeStream stream,
            HyperspaceDuplexFlowContext duplexFlowContext,
            ChannelPipeline pipeline, EndpointProtocolEnum to) {
        if (to.getIndex() < HTTP2.getIndex()) {
            pipeline.remove(TARGET_HTTP1_CODEC_HANDLER_NAME);
            return;
        }
        pipeline.remove(TARGET_HTTP1_CODEC_HANDLER_NAME);
        pipeline.remove(TARGET_HANDLER_ADAPTER_NAME);
        configHttp2Pipeline(pipeline, targetEndpointNode.getNodeConfig().getProtocolConfig());
        AbstractTargetProtocolAdapter targetProtocolAdapter =
                (AbstractTargetProtocolAdapter)pipeline.get(TARGET_HANDLER_ADAPTER_NAME);
        targetProtocolAdapter.resetChannelActiveState(pipeline.context(targetProtocolAdapter),
                stream, duplexFlowContext);
    }

}
